/*******************************************************************************
 **
 ** Advanced Distributed Learning Co-Laboratory (ADL Co-Lab) Hub grants you 
 ** ("Licensee") a non-exclusive, royalty free, license to use, modify and 
 ** redistribute this software in source and binary code form, provided that 
 ** i) this copyright notice and license appear on all copies of the software; 
 ** and ii) Licensee does not utilize the software in a manner which is 
 ** disparaging to ADL Co-Lab Hub.
 **
 ** This software is provided "AS IS," without a warranty of any kind.  ALL 
 ** EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING 
 ** ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 ** OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.  ADL Co-Lab Hub AND ITS LICENSORS 
 ** SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
 ** USING, MODIFYING OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES.  IN NO 
 ** EVENT WILL ADL Co-Lab Hub OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, 
 ** PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 ** INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE 
 ** THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE 
 ** SOFTWARE, EVEN IF ADL Co-Lab Hub HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 ** DAMAGES.
 **
 *******************************************************************************/
package org.adl.util.zip;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.zip.ZipException;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;

/**
 * 
 * <strong>Filename:</strong><br>
 * UnZipHandler.java<br>
 * <br>
 * 
 * <strong>Description:</strong><br>
 * A <code>UnZipHandler</code> provides the ability to extract the contents of a
 * zipped file to a given directory.<br>
 * <br>
 * 
 * <strong>Side Effects:</strong><br>
 * The files will be extracted to the given directory
 * 
 * @author ADL Technical Team
 */
@Slf4j
public class UnZipHandler {
	/**
	 * Buffer size for use in extracting Zip File
	 */
	public static final int BUFFER_SIZE = 16384;

	/**
	 * A reference to the Zip File to be extracted
	 */
	private File mZipFile;

	/**
	 * The directory to which the Zip File will be extracted to
	 */
	private String mExtractToDir;

	/**
	 * The encoding of the zip file
	 */
	@Setter
    @Getter
    private String encoding = StandardCharsets.UTF_8.name();

	/**
	 * Constructor for the <code>UnZipHandler</code> class.
	 * 
	 * @param iZipFileName
	 *            Name and path of the <code>.zip</code> file.
	 * @param iTargetDirName
	 *            Name and path of the directory to extract the contents of the
	 *            Zip File to.
	 */
	public UnZipHandler(String iZipFileName, String iTargetDirName, String encoding) {
		setFile(iZipFileName);
		setTargetDirectory(iTargetDirName);
		setEncoding(encoding);
	}

	/**
	 * Extracts the Zip File into the destination directory
	 * 
	 */
	public void extract() {
		String fileName = "";
		String destFileName = "";

		// Create a byte buffer
		byte[] buffer = new byte[BUFFER_SIZE];

        try (ZipFile archive = new ZipFile(mZipFile, encoding)) {
            for (Enumeration<?> e = archive.getEntries(); e.hasMoreElements(); ) {
                // Get the next entry in the Zip File
                ZipEntry entry = (ZipEntry) e.nextElement();

                if (!entry.isDirectory()) {
                    fileName = entry.getName();
                    fileName = fileName.replace('/', File.separatorChar);

                    destFileName = mExtractToDir + fileName;

                    File destFile = new File(destFileName);

                    // Create the destination path, if needed
                    String parent = destFile.getParent();
                    if (parent != null) {
                        File parentFile = new File(parent);
                        if (!parentFile.exists()) {
                            // Create the chain of sub-directories to the file
                            parentFile.mkdirs();
                        }
                    }

                    // Get a stream of the archive entry's bytes
                    try (InputStream in = archive.getInputStream(entry);
                         FileOutputStream out = new FileOutputStream(destFileName)) {

                        // Repeat reading into buffer and writing buffer to file,
                        // until done. Count will always be # bytes read, until
                        // EOF when it is -1.
                        int count;
                        while ((count = in.read(buffer)) != -1) {
                            out.write(buffer, 0, count);
                        }
                    }
                }
            }
        } catch (NullPointerException | IOException | SecurityException ze) {
            log.warn("Could not extract zip file [{}]", mZipFile, ze);
        }
    }

    /**
	 * Returns the target directory of the extracted contents of the Zip File.
	 * 
	 * @return target The target directory of the extracted contents of the Zip
	 *         File.
	 */
	public String getTargetDirectory() {
		return mExtractToDir;
	}

    /**
	 * Set the name and location of the Zip File to be extracted.
	 * 
	 * @param iFileName
	 *            Name and location of the Zip File to be extracted.
	 */
	private void setFile(String iFileName) {
		try {
			mZipFile = new File(iFileName);
		} catch (NullPointerException npe) {
			log.warn("Could not set zip file [{}], {}", iFileName, npe.toString());
		}
	}

	/**
	 * Set the target directory of the extracted contents of the Zip File.
	 * 
	 * @param iTargetDirPath
	 *            The target directory of the extracted contents of the Zip
	 *            File.
	 * 
	 */
	private void setTargetDirectory(String iTargetDirPath) {
		try {
			mExtractToDir = iTargetDirPath;
		} catch (NullPointerException npe) {
			log.warn("Could not set target directory [{}], {}", iTargetDirPath, npe.toString());
		}
	}
}